//
// Created by goksu on 2/25/20.
//

#include <fstream>
#include "Scene.hpp"
#include "Renderer.hpp"
#include <thread>
#include <pthread.h>


inline float deg2rad(const float& deg) { return deg * M_PI / 180.0; }

const float EPSILON = 0.00001;
float wirting_file = false;

void Renderer::Subrender(const Scene& scene)
{
    std::cout << "sub render start" << "\n";
    while (true)
    {
        mt_subrender_rect_pool.lock();
        if (subrender_rect_pool.size() == 0)
        {
            mt_subrender_rect_pool.unlock();
            break;
        }
        int * temp = subrender_rect_pool.back().edge;
        subrender_rect_pool.pop_back();
        mt_subrender_rect_pool.unlock();
        
        int y_start = temp[0], y_end = temp[1], x_start = temp[2], x_end = temp[3];
        
        for (size_t k = 0; k < spp; k++)
        {
            for (uint32_t j = y_start; j < y_end; ++j) {
                for (uint32_t i = x_start; i < x_end; ++i) {
                    // generate primary ray direction
                    float x = (2 * (i + 0.5) / (float)scene.width - 1) *
                            imageAspectRatio * scale;
                    float y = (1 - 2 * (j + 0.5) / (float)scene.height) * scale;

                    Vector3f dir = normalize(Vector3f(-x, y, 1));
                    framebuffer[j * scene.width + i] += scene.castRay(Ray(eye_pos, dir), 0) / spp;  
                }
            }
            
            UpdateProgress(float(k) / float(spp));
            
            if (wirting_file)
            {
                continue;
            }
            
            mt_write_file.lock();
            wirting_file = true;
            // save framebuffer to file
            FILE* fp = fopen("binary.ppm", "wb");
            (void)fprintf(fp, "P6\n%d %d\n255\n", scene.width, scene.height);
            for (auto i = 0; i < scene.height * scene.width; ++i) {
                static unsigned char color[3];
                color[0] = (unsigned char)(255 * std::pow(clamp(0, 1, framebuffer[i].x), 0.6f));
                color[1] = (unsigned char)(255 * std::pow(clamp(0, 1, framebuffer[i].y), 0.6f));
                color[2] = (unsigned char)(255 * std::pow(clamp(0, 1, framebuffer[i].z), 0.6f));
                fwrite(color, 1, 3, fp);
            }
            fclose(fp); 
            wirting_file = false;
            mt_write_file.unlock();
        }
    }
}

// The main render function. This where we iterate over all pixels in the image,
// generate primary rays and cast these rays into the scene. The content of the
// framebuffer is saved to a file.
void Renderer::Render(const Scene& scene)
{
    framebuffer = std::vector<Vector3f>(scene.width * scene.height);

    scale = tan(deg2rad(scene.fov * 0.5));
    imageAspectRatio = scene.width / (float)scene.height;
    eye_pos = Vector3f(278, 273, -800);

    // change the spp value to change sample ammount
    spp = 1024;
    std::cout << "SPP: " << spp << "\n";

    subrender_rect_size = 200;
    
    int j, i;
    for (j = 0; j < scene.height - subrender_rect_size; j += subrender_rect_size)
    {
        for (i = 0; i < scene.width - subrender_rect_size; i += subrender_rect_size)
        {
            subrender_rect_pool.push_back({j, j + subrender_rect_size, i, i + subrender_rect_size});
        }
        subrender_rect_pool.push_back({j, j + subrender_rect_size, i, scene.width});
    }
    for (i = 0; i < scene.width - subrender_rect_size; i += subrender_rect_size)
    {
        subrender_rect_pool.push_back({j, scene.height, i, i + subrender_rect_size});
    }
    subrender_rect_pool.push_back({j, scene.height, i, scene.width});
    std::reverse(subrender_rect_pool.begin(), subrender_rect_pool.end());
    rect_count = subrender_rect_pool.size();
    std::cout << "Rect Count: " << rect_count  << "\n";

    thread_count = 6;
    std::vector<std::thread> thread_pool;
    for (size_t i = 0; i < thread_count; i++)
    {
        std::cout << "Thread Count: " << thread_count << " " << i  << "\n";
        thread_pool.push_back(std::thread([this, &scene]() -> void {
            Subrender(scene);
        }));
        std::cout << "Thread Count: " << thread_count << " " << i  << "\n";
    }
    for (size_t i = 0; i < thread_pool.size(); i++)
    {
        thread_pool[i].join();
    }
}
